/*
 *  policy - Part of the Policy Server Application
 *  Copyright (C) 2009 Liam Lowey
 *  
 *      http://policyserver.turnitin.co.uk/
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); 
 *  you may not use this file except in compliance with the License. 
 *  You may obtain a copy of the License at 
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0 
 *  
 *  Unless required by applicable law or agreed to in writing, software 
 *  distributed under the License is distributed on an "AS IS" BASIS, 
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 *  See the License for the specific language governing permissions and 
 *  limitations under the License. 
 */

using System;
using System.Configuration;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Net;
using System.IO;

namespace policyserver
{
  class Program : Observer
  {
    private const int REACTOR_COUNT = 10;
    private static readonly IPAddress BIND_TO = IPAddress.Any;
    private static readonly int[] LISTEN_PORTS = new int[] { 843, 943 };
    private const string DEFAULT_PAYLOAD = "masterpolicyfile.xml";

    private int _reactorCount = REACTOR_COUNT;
    private IPAddress _bindTo = BIND_TO;
    private int[] _listenPorts = LISTEN_PORTS;
    private string _payloadFileName = DEFAULT_PAYLOAD;

    private Listener [] _listeners;
    private Reactor [] _reactors;
    private int _lastReactor = 0;
    private byte[] _payload;

    private object _reactorLock = new object();

    public Program()
    {
      parseSettings();
      startServer();
    }


    private void parseSettings()
    {
      string listeningPorts = getSettingSafe( "listeningPorts" );
      if( listeningPorts.Length > 0 )
      {
        List<int> newPortsList = new List<int>();

        string [] portList = listeningPorts.Split( new char[] { ',', ';' } );
        foreach( string port in portList )
        {
          int nextPort;
          if( int.TryParse( port, out nextPort ) )
          {
            newPortsList.Add( nextPort );
          }
          else
          {
            trace( "Don't understand port \"{0}\".", port );
          }
        }


        if( newPortsList.Count > 0 )
        {
          _listenPorts = newPortsList.ToArray();
        }
      }


      string bindAddress = getSettingSafe( "bindTo" );
      if( bindAddress.Length > 0 )
      {
        IPAddress bindTo;

        if( IPAddress.TryParse( bindAddress, out bindTo ) )
        {
          _bindTo = bindTo;
        }
        else
        {
          trace( "Don't understand bind address: {0}.", bindAddress );
        }
      }


      string threadPoolCount = getSettingSafe( "threadPoolCount" );
      if( threadPoolCount.Length > 0 )
      {
        int reactorCount;
        if( int.TryParse( threadPoolCount, out reactorCount ) && reactorCount > 0 && reactorCount < 100 )
        {
          _reactorCount = reactorCount;
        }
        else
        {
          trace( "Don't understand threadPoolCount: {0}.", threadPoolCount );
        }  
      }


      string policyFile = getSettingSafe( "policyFile" );
      if( policyFile.Length > 0 )
      {
        if( File.Exists( policyFile ) )
        {
          _payloadFileName = policyFile;
        }
        else
        {
          trace( "Don't recognise policy file: {0}.", policyFile );
        }
      }

      StringBuilder portsList = new StringBuilder();
      foreach(int nextPort in _listenPorts)
      {
          portsList.Append(nextPort + ",");
      }

      trace("parseSettings() - Listen ports: {0}.", portsList.ToString());
      trace( "parseSettings() - Bind to: {0}.", _bindTo );
      trace( "parseSettings() - Reactor count: {0}.", _reactorCount );
      trace( "parseSettings() - Policy file: {0}.", _payloadFileName );
    }


    private static string getSettingSafe( string setting )
    {
      try
      {
        return ConfigurationManager.AppSettings[setting];
      }
      catch( ConfigurationErrorsException )
      {
        trace( "Setting \"{0}\" is not available, using default.", setting );
      }

      return "";
    }


    private void startServer()
    {
      try
      {
        trace( "startServer() - Policy Server Startup." );

        loadPayload();

        trace( "startServer() - Starting {0} reactors.", _reactorCount );

        _reactors = new Reactor[_reactorCount];
        for( int reactorCount = 0; reactorCount < _reactorCount; ++reactorCount )
        {
          _reactors[reactorCount] = new Reactor( reactorCount );
        }

        _listeners = new Listener[_listenPorts.Length];
        for( int listenerCount = 0; listenerCount < _listenPorts.Length; ++listenerCount )
        {
          trace( "startServer() - Starting listener on ip: {0} and port: {1}.", _bindTo, _listenPorts[listenerCount] );

          _listeners[listenerCount] = new Listener( this, _bindTo, _listenPorts[listenerCount], _payload );
        }

        trace( "startServer() - Startup complete." );
      }
      catch( Exception e )
      {
        trace( "startServer() - Error: " + e.Message );
        stop();
        throw;
      }
    }


    public void addConnection( Connection obs )
    {
      lock( _reactorLock )
      {
        _reactors[_lastReactor++].addConnection( obs );

        if( _lastReactor >= REACTOR_COUNT )
        {
          _lastReactor = 0;
        }
      }
    }


    private void stop()
    {
      foreach( Listener nextListener in _listeners )
      {
        if( nextListener != null )
        {
          nextListener.stop();
        }
      }
      foreach( Reactor nextReactor in _reactors )
      {
        if( nextReactor != null )
        {
          nextReactor.stop();
        }
      }
    }


    public void run()
    {
      Console.CancelKeyPress += delegate
      {
        stop();
      };
      Thread.Sleep( Timeout.Infinite );      
    }


    private void loadPayload()
    {
      trace( "loadPayload() - Loading payload: {0}.", _payloadFileName );

      FileStream stream = File.OpenRead( _payloadFileName );

      TextReader reader = new StreamReader( stream );

      string theFile = reader.ReadToEnd();

      reader.Close();

      stream.Close();

      _payload = new byte[theFile.Length + 1];

      int index = 0;

      foreach( char seq in theFile )
      {
        _payload[index++] = (byte)seq;
      }

      _payload[index] = 0;

      trace( "loadPayload() - Payload is {0} bytes.", _payload.Length );
    }


    private static void trace( string format, params object[] args )
    {
      System.Console.WriteLine( format, args );
    }


    static void Main()
    {
      try
      {
        Program p = new Program();
        p.run();
      }
      catch { }
    }
  }
}
